问题提出:
给定两字符串 s[], t[]. 实现函数 find( const char s[], const char t[], int pos ),功能为从字符串s[] 的第pos个位置起(按照C++中数组下标规定,以0开始计数)开始查找t[]在s[]中第一次出现的位置,若干s[]中不存在t[],返回-1.
// 解法源代码下载地址:KMP.cpp & KMP.exe
分析:这道题很自然的想法就是 查找-回溯,代码见 find_1 函数。此算法最易想到,可是时间复杂度为 O(m*n),其中m,n 分别为s[],t[]的大小。
此类字符匹配有一个经典算法,KMP算法(由Knuth、Morris、Pratt共同提出)。这个算法非常好的“预处理”了要用于查找的t[].即发现t[]串的特点,这个特点能够使查找时指示s[]的指针i不回溯,只移动用于指示t[]的指针j。
next[] 算法:
1、next[0]=-1, next[1]=0;令k=next[j],
2、 如果 t[k]=t[j], 则 next[j+1]=k+1;
3、否则令k=next[k],比较 t[ k] ]=t[j],等则next[j+1]=k+1,不等则继续此步操作。
4、若一直不等,则 next[j+1]=0;
nextval1[] 算法:( 在已经求得next[]的基础上)
1、令k=next[j], 如果 t[k]==t[j], 令k=next[k],比较t[k] 与 t[j];若等,继续此操作直到不等的情况。
2、若不能,则next[j]=k;
当然,你应该已经看出其实next[] 和 nextval1[]的运算可以合并为一个函数完成,那就是get_nextval[] 函数。
在纸上画出 s[], t[]就可以体会为什么能够这样运算,我的理解是尽量使j值更小,也就是不断地切分t[]从0开始的一串(和从t[j-1]倒数相同的一串)。使这串的值最小。
以下是实现的全部代码》
find_1 普通实现方法。用count1记录“基本运算”(比较)次数,代码中的次数为56。
find_KMP :KMP算法,当使用未优化的KMP使 count2 记录为 36 , 当用优化的KMP算法时 count2记录为24.
// 因为数组从 0 开始计数,所以代码中一些地方使用了-1作为标志。
#include <iostream>
using namespace std;
int count1 =0, count2=0;
int find_1( const char s[], const char t[], int pos )
{
int i = pos, j =0;
while( s[i]!='/0' && t[j]!='/0' )
{
if( s[i] == t[j] ) { ++i; ++j; }
else { i = i-( j-1 ); j=0; } // 这里的i 和后面放回时i 的值最好用特殊法定,既方便又不易出错。
++count1;
}
if( t[j]=='/0' ) return i-j; // 这句不能放在while循环里,否则无法判断找不到匹配字符串的情况。
else return -1; // -1 表示没有匹配字符
}
void get_next( const char t[ ], int next[ ] ) // 未优化的next[ ]
{
int j = 0, k = *(next+0) = -1; // 不能直接定义出next[1]=0, 因为有可能要查找的串只有一个字符。
while( t[j+1] != '/0' )
{
if( k==-1 || t[j]==t[k] )
{
++j; ++k; *(next+j) = k; // j>=1 时,等价于“若满足则next[j+1]=next[j]+1; ”.
}
else k=*(next+k); // 不满足时,j 不变(因为没有进入上面的if语句),k 赋值为next[k], 继续进入if 比较t[j]==t[k].等价于分析时的不等则t[j] == t[ next[k] ];
}
}
void get_nextval( const char t[ ], int next[ ] ) // 一步实现优化
{
int j = 0, k = -1; *(next+0)=-1;
while( t[j+1]!='/0' )
{
if( k==-1 || t[j]==t[k] )
{
++j; ++k;
if( t[j] !=t[k] ) *(next+j)=k;
else *(next+j) = *(next+k);
}
else k=*(next+k);
}
}
void get_nextval1( const char t[ ], int next[ ] ) // 在已知next[]基础上实现优化
{
int j =0, k;
while( t[j+1] != '/0' )
{
if( j==0 ) { ++j; continue; } // 若是第一个位置,则掠过。
k = *(next+j);
while( t[j]==t[k] ) { k=*(next+k); }
*(next+j) = k;
++j;
}
}
int find_KMP( const char s[], const char t[], int pos )
{
int size = sizeof(t)/sizeof(t[0]) -1;
int * next = new int[size];
get_nextval( t, next ); // 一步实现优化next[]
// get_next( t, next ); get_nextval1( t, next ); // 两部实现优化next[]
// get_next( t, next ); // 未优化的next[]
int i = pos, j =0;
while( s[i]!='/0' && t[j]!='/0' )
{
if( j==-1 || s[i] == t[j] ) { ++i; ++j; }
else j = *(next+j);
++count2;
}
if( t[j]=='/0' ) return i-j;
else return -1;
}
void print( int a[], int n )
{
for ( int i =0; i<n; ++i )
cout<<a[i]<<'/t'<<flush;
cout<<endl;
}
void print1( char a[], int n )
{
for ( int i =0; i<n; ++i )
cout<<a[i]<<" "<<flush;
cout<<endl;
}
void main()
{
char s[]="aaaabaaaabaaaabaaaaab";
char a[7] = "aaaaab";
int n[6];
cout<<"待查找串为:"<<endl; print1( s, 22 );
cout<<"模式串为"<<endl; print1( a,7);
get_next( a, n );
cout<<"未优化时的next[]值"<<endl;
print( n, 6 );
get_nextval1( a, n );
cout<<"优化时的next[]值"<<endl;
print( n, 6 );
cout<<"t[]在s[]中出现的位置为:"<<find_1( s, a, 0 )<<endl;
cout<<"普通方法使用了:"<<count1<<"步"<<endl;
cout<<endl;
cout<<"t[]在s[]中出现的位置为:"<<find_KMP( s, a, 0 )<<endl;
cout<<"KMP优化方法使用了:"<<count2<<"步"<<endl;
cin.get();
}
// KMP.exe has encountered a problem and needs to close. We are sorry for the inconvenience.
// 可能错误原因:数组越界:因为我用-1 做了一些标记。